﻿///////////////////////////////////////////////////////////////////////////////////////////////
// System.Text.Parsing - © 2011 Sebastien Pellizzari
// Released under the MIT License (http://www.opensource.org/licenses/mit-license.php)
///////////////////////////////////////////////////////////////////////////////////////////////
using System.CodeDom.Compiler;
using System.IO;
using System.Text.RegularExpressions;
using System.Collections.Generic;

namespace System.Text.Parsing
{
    /// <summary>Represents the grammar processing class for Regex text tokenizers.</summary>
    public sealed class RegexTextTokenizerGrammarProcessor : GrammarTokenizerProcessor
    {
        //-----------------------------------------------------------------------
        //     Methods
        //-----------------------------------------------------------------------

        // GenerateDesignTimeSourceCode method.
        public override bool GenerateDesignTimeSourceCode(string languageName, string codeNamespace, TextWriter textWriter, GrammarTokenizer grammar)
        {
            // Check grammar.
            CheckedGrammar checkedGrammar;
            if (!CheckGrammar(grammar, out checkedGrammar)) return false;

            // If C#...
            if (languageName == "C#")
            {
                return GenerateSourceCodeCSharp(codeNamespace, textWriter, checkedGrammar, true);
            }
            else
            {
                EmitError(GrammarSourceRange.Unknown, "language {0} is not supported by this type of tokenizer", languageName);
                return false;
            }
        }

        // GenerateCompileTimeSourceCode method.
        public override bool GenerateCompileTimeSourceCode(string languageName, string codeNamespace, TextWriter textWriter, GrammarTokenizer grammar)
        {
            // Check grammar.
            CheckedGrammar checkedGrammar;
            if (!CheckGrammar(grammar, out checkedGrammar)) return false;

            // If C#...
            if (languageName == "C#")
            {
                return GenerateSourceCodeCSharp(codeNamespace, textWriter, checkedGrammar, false);
            }
            else
            {
                EmitError(GrammarSourceRange.Unknown, "language {0} is not supported by this type of tokenizer", languageName);
                return false;
            }
        }

        //-----------------------------------------------------------------------
        //     Non Public (Grammar Checking)
        //-----------------------------------------------------------------------

        public sealed class CheckedTokenType
        {
            public bool IsRefType;
            public string FieldName;
            public List<int> TokenIndices = new List<int>();
            public GrammarTextValue TypeName;
        }

        public sealed class CheckedTokenCodeBlock
        {
            public int Index;
            public GrammarCodeBlock Definition;
            public CheckedTokenType UsedTokenType;
            public List<int> TokenIndices = new List<int>();
        }

        public sealed class CheckedToken
        {
            public int Index;
            public bool Ignore;
            public bool NotRegex;
            public bool CaseInsensitive;
            public string TokenRegexString;
            public GrammarToken Definition;
            public CheckedTokenType RefType;
            public CheckedTokenType ValueType;
            public CheckedTokenCodeBlock ComputeCodeBlock;
            public CheckedTokenCodeBlock ValidateCodeBlock;
        }

        private sealed class CheckedGrammar
        {
            public bool IsPublic;
            public bool HasRefTypes;
            public bool HasValueTypes;
            public string ClassName;
            public GrammarCodeBlock Namespaces;
            public GrammarCodeBlock UserClassCode;
            public List<CheckedToken> TokenList = new List<CheckedToken>();
            public Dictionary<string, CheckedToken> TokensByName = new Dictionary<string, CheckedToken>();
            public Dictionary<string, CheckedTokenType> DataTypes = new Dictionary<string, CheckedTokenType>();
            public Dictionary<GrammarCodeBlock, CheckedTokenCodeBlock> ComputeCodeBlocks = new Dictionary<GrammarCodeBlock, CheckedTokenCodeBlock>();
            public Dictionary<GrammarCodeBlock, CheckedTokenCodeBlock> ValidateCodeBlocks = new Dictionary<GrammarCodeBlock, CheckedTokenCodeBlock>();
        }

        // CheckGrammar method.
        private bool CheckGrammar(GrammarTokenizer grammar, out CheckedGrammar checkedGrammar)
        {
            var valid = true;
            checkedGrammar = new CheckedGrammar();
            checkedGrammar.ClassName = grammar.Name.Text;

            // Check grammar properties.
            foreach (var property in grammar.Properties)
                switch (property.Name.Text)
                {
                    case "GeneratePublicClass":
                        if (!String.IsNullOrWhiteSpace(property.Value.Text))
                        {
                            EmitError(property.Value.SourceInfo, "the 'GeneratePublicClass' property does not need a value");
                            valid = false;
                        }
                        checkedGrammar.IsPublic = true;
                        break;
                    default:
                        EmitError(property.Name.SourceInfo, "unknown property '{0}'", property.Name.Text);
                        valid = false;
                        break;
                }

            // Check grammar code blocks.
            foreach (var codeBlock in grammar.CodeBlocks)
                switch (codeBlock.Name.Text)
                {
                    case "":
                        checkedGrammar.UserClassCode = codeBlock;
                        break;
                    case "Namespaces":
                        checkedGrammar.Namespaces = codeBlock;
                        break;
                    default:
                        EmitError(codeBlock.Name.SourceInfo, "unknown code block '{0}'", codeBlock.Name.Text);
                        valid = false;
                        break;
                }

            // Check tokens.
            foreach (var token in grammar.Tokens)
            {
                // Create checked token object.
                var checkedToken = new CheckedToken();
                checkedToken.Index = checkedGrammar.TokenList.Count;
                checkedToken.Definition = token;

                // Add it to the lists.
                checkedGrammar.TokenList.Add(checkedToken);
                checkedGrammar.TokensByName.Add(token.Name.Text, checkedToken);

                // Make sure the name is not reserved.
                if (token.Name.Text == "EOF" || token.Name.Text == "ERROR")
                {
                    EmitError(token.Name.SourceInfo, "unexpected reserved name '{0}'", token.Name.Text);
                    valid = false;
                }

                // Flatten group properties and codeblocks.
                token.FlattenProperties();
                token.FlattenCodeBlocks();

                // Check token properties.
                foreach (var property in token.Properties)
                    switch (property.Name.Text)
                    {
                        case "RefType":
                            if (String.IsNullOrWhiteSpace(property.Value.Text))
                            {
                                EmitError(property.Value.SourceInfo, "the 'RefType' property requires a value");
                                valid = false;
                            }
                            {
                                CheckedTokenType tokenType;
                                if (!checkedGrammar.DataTypes.TryGetValue(property.Value.Text, out tokenType))
                                {
                                    tokenType = new CheckedTokenType();
                                    tokenType.IsRefType = true;
                                    tokenType.TypeName = property.Value;
                                    checkedGrammar.DataTypes.Add(property.Value.Text, tokenType);
                                    checkedGrammar.HasRefTypes = true;
                                }
                                if (!tokenType.IsRefType)
                                {
                                    EmitError(property.Value.SourceInfo, "type {0} used as a reference type but used as a value type in a previous declaration");
                                    valid = false;
                                }
                                tokenType.TokenIndices.Add(checkedToken.Index);
                                checkedToken.RefType = tokenType;
                            }
                            break;
                        case "ValueType":
                            if (String.IsNullOrWhiteSpace(property.Value.Text))
                            {
                                EmitError(property.Value.SourceInfo, "the 'ValueType' property requires a value");
                                valid = false;
                            }
                            {
                                CheckedTokenType tokenType;
                                if (!checkedGrammar.DataTypes.TryGetValue(property.Value.Text, out tokenType))
                                {
                                    tokenType = new CheckedTokenType();
                                    tokenType.IsRefType = false;
                                    tokenType.TypeName = property.Value;
                                    checkedGrammar.DataTypes.Add(property.Value.Text, tokenType);
                                    checkedGrammar.HasValueTypes = true;
                                }
                                if (tokenType.IsRefType)
                                {
                                    EmitError(property.Value.SourceInfo, "type {0} used as a value type but used as a value type in a previous declaration");
                                    valid = false;
                                }
                                tokenType.TokenIndices.Add(checkedToken.Index);
                                checkedToken.ValueType = tokenType;
                            }
                            break;
                        case "Ignore":
                            if (!String.IsNullOrWhiteSpace(property.Value.Text))
                            {
                                EmitError(property.Value.SourceInfo, "the 'Ignore' property does not need a value");
                                valid = false;
                            }
                            checkedToken.Ignore = true;
                            break;
                        case "NotRegex":
                            if (!String.IsNullOrWhiteSpace(property.Value.Text))
                            {
                                EmitError(property.Value.SourceInfo, "the 'NotRegex' property does not need a value");
                                valid = false;
                            }
                            checkedToken.NotRegex = true;
                            break;
                        case "CaseInsensitive":
                            if (!String.IsNullOrWhiteSpace(property.Value.Text))
                            {
                                EmitError(property.Value.SourceInfo, "the 'CaseInsensitive' property does not need a value");
                                valid = false;
                            }
                            checkedToken.CaseInsensitive = true;
                            break;
                        default:
                            EmitError(property.Name.SourceInfo, "unknown property '{0}'", property.Name.Text);
                            valid = false;
                            break;
                    }

                // Check token codeblocks.
                foreach (var codeBlock in token.CodeBlocks)
                    switch (codeBlock.Name.Text)
                    {
                        case "Compute":
                            {
                                CheckedTokenCodeBlock checkedCodeBlock;
                                if (!checkedGrammar.ComputeCodeBlocks.TryGetValue(codeBlock, out checkedCodeBlock))
                                {
                                    checkedCodeBlock = new CheckedTokenCodeBlock();
                                    checkedCodeBlock.Index = checkedGrammar.ComputeCodeBlocks.Count;
                                    checkedCodeBlock.Definition = codeBlock;
                                    checkedGrammar.ComputeCodeBlocks.Add(codeBlock, checkedCodeBlock);
                                }
                                checkedCodeBlock.TokenIndices.Add(checkedToken.Index);
                                checkedToken.ComputeCodeBlock = checkedCodeBlock;
                            }
                            break;
                        case "Validate":
                            {
                                CheckedTokenCodeBlock checkedCodeBlock;
                                if (!checkedGrammar.ValidateCodeBlocks.TryGetValue(codeBlock, out checkedCodeBlock))
                                {
                                    checkedCodeBlock = new CheckedTokenCodeBlock();
                                    checkedCodeBlock.Index = checkedGrammar.ValidateCodeBlocks.Count;
                                    checkedCodeBlock.Definition = codeBlock;
                                    checkedGrammar.ValidateCodeBlocks.Add(codeBlock, checkedCodeBlock);
                                }
                                checkedCodeBlock.TokenIndices.Add(checkedToken.Index);
                                checkedToken.ValidateCodeBlock = checkedCodeBlock;
                            }
                            break;
                        default:
                            EmitError(codeBlock.Name.SourceInfo, "unknown code block '{0}'", codeBlock.Name.Text);
                            valid = false;
                            break;
                    }

                // Check codeblocks and types.
                if (checkedToken.RefType != null && checkedToken.ValueType != null)
                {
                    EmitError(checkedToken.Definition.SourceInfo, "token {0} has both a RefType and a ValueType property", checkedToken.Definition.Name.Text);
                    valid = false;
                }
                if (checkedToken.ComputeCodeBlock != null)
                {
                    if (checkedToken.RefType == null && checkedToken.ValueType == null)
                    {
                        EmitError(checkedToken.Definition.SourceInfo, "token {0} has a Compute code block but no RefType or ValueType property", checkedToken.Definition.Name.Text);
                        valid = false;
                    }
                    else
                    {
                        var tokenType = checkedToken.RefType != null ? checkedToken.RefType : checkedToken.ValueType;
                        if (checkedToken.ComputeCodeBlock.UsedTokenType == null)
                            checkedToken.ComputeCodeBlock.UsedTokenType = tokenType;
                        else if (checkedToken.ComputeCodeBlock.UsedTokenType != tokenType)
                        {
                            EmitError(checkedToken.ComputeCodeBlock.Definition.SourceInfo, "Compute code block is used with different data types (ValueType or RefType)");
                            valid = false;
                        }
                    }
                }
            }

            return valid;
        }

        //-----------------------------------------------------------------------
        //     Non Public (C# Generation)
        //-----------------------------------------------------------------------

        // GenerateSourceCodeCSharp method.
        private bool GenerateSourceCodeCSharp(string codeNamespace, TextWriter textWriter, CheckedGrammar grammar, bool isDesignTime)
        {
            var indentedWriter = new IndentedTextWriter(textWriter);

            // Write header.
            indentedWriter.WriteLine("// Auto-generated code");
            indentedWriter.WriteLine();

            // Write Usings.
            indentedWriter.WriteLine("using System.Text.Parsing;");
            indentedWriter.WriteLine("using System.Text.RegularExpressions;");
            if (grammar.Namespaces != null)
                WriteCSharpCode(indentedWriter, grammar.Namespaces.Code);
            indentedWriter.WriteLine();

            // Open namespace.
            if (!String.IsNullOrWhiteSpace(codeNamespace))
            {
                indentedWriter.WriteLine("namespace {0}", codeNamespace);
                indentedWriter.WriteLine("{");
                indentedWriter.Indent++;
            }

            // Write class and open braces.
            if (isDesignTime)
            {
                indentedWriter.WriteLine("/// <summary>Represents an implementation of the {0} tokenizer.</summary>", grammar.ClassName);
                indentedWriter.WriteLine("{0}partial class {1} : RegexTextTokenizer<{1}.TokenValue, {1}>, RegexTextTokenizer<{1}.TokenValue, {1}>.ICallback", grammar.IsPublic ? "public " : String.Empty, grammar.ClassName);
            }
            else
            {
                indentedWriter.WriteLine("partial class {0} : RegexTextTokenizer<{0}.TokenValue, {0}>, RegexTextTokenizer<{0}.TokenValue, {0}>.ICallback", grammar.ClassName);
            }
            indentedWriter.WriteLine("{");
            indentedWriter.Indent++;

            // Write user code.
            if (isDesignTime && grammar.UserClassCode != null)
            {
                indentedWriter.WriteLine("#region User Defined Code");
                WriteCSharpCode(indentedWriter, grammar.UserClassCode.Code);
                indentedWriter.WriteLine("#endregion");
                indentedWriter.WriteLine();
            }

            // Write token value.
            if (isDesignTime)
            {
                indentedWriter.WriteLine("/// <summary>Represents a container type for values of a token.</summary>");
                indentedWriter.WriteLine("public partial struct TokenValue");
            }
            else
            {
                indentedWriter.WriteLine("partial struct TokenValue");
            }
            indentedWriter.WriteLine("{");
            indentedWriter.Indent++;
            if (!isDesignTime)
            {
                // Write reference types.
                if (grammar.HasRefTypes)
                {
                    indentedWriter.WriteLine("[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)]");
                    indentedWriter.WriteLine("public struct RefTypes");
                    indentedWriter.WriteLine("{");
                    indentedWriter.Indent++;
                    var index = 0;
                    foreach (var dataType in grammar.DataTypes.Values)
                        if (dataType.IsRefType)
                        {
                            dataType.FieldName = String.Format("RefType.Value{0}", index);
                            indentedWriter.WriteLine("[System.Runtime.InteropServices.FieldOffset(0)]");
                            indentedWriter.WriteLine("public");
                            WriteCSharpCode(indentedWriter, dataType.TypeName);
                            indentedWriter.WriteLine("Value{0};", index);
                            index++;
                        }
                    indentedWriter.Indent++;
                    indentedWriter.WriteLine("}");
                    indentedWriter.WriteLine("public RefTypes RefType;");
                }
                // Write value types.
                if (grammar.HasValueTypes)
                {
                    indentedWriter.WriteLine("[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)]");
                    indentedWriter.WriteLine("public struct ValueTypes");
                    indentedWriter.WriteLine("{");
                    indentedWriter.Indent++;
                    var index = 0;
                    foreach (var dataType in grammar.DataTypes.Values)
                        if (!dataType.IsRefType)
                        {
                            dataType.FieldName = String.Format("ValueType.Value{0}", index);
                            indentedWriter.WriteLine("[System.Runtime.InteropServices.FieldOffset(0)]");
                            indentedWriter.WriteLine("public");
                            WriteCSharpCode(indentedWriter, dataType.TypeName);
                            indentedWriter.WriteLine("Value{0};", index);
                            index++;
                        }
                    indentedWriter.Indent++;
                    indentedWriter.WriteLine("}");
                    indentedWriter.WriteLine("public ValueTypes ValueType;");
                }
            }
            indentedWriter.Indent--;
            indentedWriter.WriteLine("}");
            indentedWriter.WriteLine();

            // Write token IDs.
            if (isDesignTime)
            {
                indentedWriter.WriteLine("/// <summary>Represents a token ID.</summary>");
                indentedWriter.WriteLine("public enum TokenID");
                indentedWriter.WriteLine("{");
                indentedWriter.Indent++;
                indentedWriter.WriteLine("ERROR = {0},", TokenTypeIds.Error);
                indentedWriter.WriteLine("EOF = {0},", TokenTypeIds.EOF);
                for (int i = 0; i < grammar.TokenList.Count; i++)
                    indentedWriter.WriteLine("{0} = {1},", grammar.TokenList[i].Definition.Name.Text, TokenTypeIds.User + i);
                indentedWriter.Indent--;
                indentedWriter.WriteLine("}");
                indentedWriter.WriteLine();
            }

            // Write static field for token table.
            if (isDesignTime)
            {
                indentedWriter.WriteLine("/// <summary>The table of tokens for this tokenizer.</summary>");
                indentedWriter.WriteLine("public static readonly TokenDefinition[] LocalTokenTable;");
                indentedWriter.WriteLine();
            }

            // Write constructor.
            if (isDesignTime)
            {
                indentedWriter.WriteLine("/// <summary>Initializes an instance of the <see cref=\"{0}\"/> class.</summary>", grammar.ClassName);
                indentedWriter.WriteLine("public {0}()", grammar.ClassName);
                indentedWriter.WriteLine("{");
                indentedWriter.Indent++;
                indentedWriter.WriteLine("base.Initialize(LocalTokenTable, this);");
                indentedWriter.Indent--;
                indentedWriter.WriteLine("}");
                indentedWriter.WriteLine();
            }

            // Write ComputeToken methods.
            if (!isDesignTime)
            {
                // Write __Compute methods.
                foreach (var computeCodeBlock in grammar.ComputeCodeBlocks.Values)
                {
                    indentedWriter.WriteLine("private void __Compute{0}(Match match, out", computeCodeBlock.Index);
                    WriteCSharpCode(indentedWriter, computeCodeBlock.UsedTokenType.TypeName);
                    indentedWriter.WriteLine("Value)");
                    indentedWriter.WriteLine("{");
                    indentedWriter.Indent++;
                    WriteCSharpCode(indentedWriter, computeCodeBlock.Definition.Code);
                    indentedWriter.Indent--;
                    indentedWriter.WriteLine("}");
                    indentedWriter.WriteLine();
                }

                // Write ComputeTokenValue method.
                indentedWriter.WriteLine("void ICallback.ComputeTokenValue(int tokenTypeId, Match match, ref TokenValue value)");
                indentedWriter.WriteLine("{");
                indentedWriter.Indent++;
                {
                    indentedWriter.WriteLine("switch (tokenTypeId)");
                    indentedWriter.WriteLine("{");
                    indentedWriter.Indent++;
                    foreach (var computeCodeBlock in grammar.ComputeCodeBlocks.Values)
                    {
                        foreach (var tokenIndex in computeCodeBlock.TokenIndices)
                            indentedWriter.WriteLine("case {0}:", TokenTypeIds.User + tokenIndex);
                        indentedWriter.Indent++;
                        indentedWriter.WriteLine("__Compute{0}(match, out value.{1});", computeCodeBlock.Index, computeCodeBlock.UsedTokenType.FieldName);
                        indentedWriter.WriteLine("break;");
                        indentedWriter.Indent--;
                    }
                    indentedWriter.Indent--;
                    indentedWriter.WriteLine("}");
                }
                indentedWriter.Indent--;
                indentedWriter.WriteLine("}");
                indentedWriter.WriteLine();
            }

            // Write ValidateToken method.
            if (!isDesignTime)
            {
                indentedWriter.WriteLine("bool ICallback.ValidateToken(int tokenTypeId, Match match)");
                indentedWriter.WriteLine("{");
                indentedWriter.Indent++;
                {
                    indentedWriter.WriteLine("bool IsValid = true;");
                    indentedWriter.WriteLine("switch (tokenTypeId)");
                    indentedWriter.WriteLine("{");
                    indentedWriter.Indent++;
                    foreach (var validateCodeBlock in grammar.ValidateCodeBlocks.Values)
                    {
                        foreach (var tokenIndex in validateCodeBlock.TokenIndices)
                            indentedWriter.WriteLine("case {0}:", TokenTypeIds.User + tokenIndex);
                        indentedWriter.Indent++;
                        indentedWriter.WriteLine("{");
                        indentedWriter.Indent++;
                        WriteCSharpCode(indentedWriter, validateCodeBlock.Definition.Code);
                        indentedWriter.Indent--;
                        indentedWriter.WriteLine("}");
                        indentedWriter.WriteLine("break;");
                        indentedWriter.Indent--;
                    }
                    indentedWriter.Indent--;
                    indentedWriter.WriteLine("}");
                    indentedWriter.WriteLine("return IsValid;");
                }
                indentedWriter.Indent--;
                indentedWriter.WriteLine("}");
                indentedWriter.WriteLine();
            }

            // Write static constructor for token table.
            if (!isDesignTime)
            {
                indentedWriter.WriteLine("static {0}()", grammar.ClassName);
                indentedWriter.WriteLine("{");
                indentedWriter.Indent++;
                {
                    indentedWriter.WriteLine("LocalTokenTable = new TokenDefinition[]");
                    indentedWriter.WriteLine("{");
                    indentedWriter.Indent++;
                    for (int i = 0; i < grammar.TokenList.Count; i++)
                    {
                        var token = grammar.TokenList[i];

                        // Build token group path string.
                        var tokenGroup = token.Definition.Group;
                        var tokenGroupPath = (String)null;
                        while (tokenGroup != null)
                        {
                            if (!String.IsNullOrWhiteSpace(tokenGroup.Name.Text))
                            {
                                if (tokenGroupPath != null)
                                    tokenGroupPath = tokenGroup.Name.Text + "." + tokenGroupPath;
                                else
                                    tokenGroupPath = tokenGroup.Name.Text;
                            }
                            tokenGroup = tokenGroup.ParentGroup;
                        }

                        // Gather options.
                        token.TokenRegexString = token.Definition.Definition.Text;

                        // Compute regex.
                        if (token.NotRegex)
                            token.TokenRegexString = Regex.Escape(token.TokenRegexString);
                        else
                        {
                            // Replace occurrences of other regexes (onlt those defined earlier).
                            for (int j = 0; j < i; j++)
                                token.TokenRegexString = token.TokenRegexString.Replace("{" + grammar.TokenList[j].Definition.Name.Text + "}", grammar.TokenList[j].TokenRegexString);
                        }

                        // Apply case insensitivity.
                        if (token.CaseInsensitive)
                            token.TokenRegexString = "(?i:" + token.TokenRegexString + ")";

                        // Write it down.
                        indentedWriter.WriteLine("new TokenDefinition(new Regex(\"^{0}\", RegexOptions.Compiled), {1}, {2}),",
                            EscapeCSharpString(token.TokenRegexString), token.Ignore ? "true" : "false",
                            tokenGroupPath == null ? "String.Empty" : '"' + EscapeCSharpString(tokenGroupPath) + '"');
                    }

                    indentedWriter.Indent--;
                    indentedWriter.WriteLine("};");
                }
                indentedWriter.Indent--;
                indentedWriter.WriteLine("}");
                indentedWriter.WriteLine();
            }

            // Close tokenizer class.
            indentedWriter.Indent--;
            indentedWriter.WriteLine("}");

            // Close namespace.
            if (!String.IsNullOrWhiteSpace(codeNamespace))
            {
                indentedWriter.Indent--;
                indentedWriter.WriteLine("}");
            }

            // Okay.
            return true;
        }

        // EscapeCSharpString method.
        private static string EscapeCSharpString(string s)
        {
            return s.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n").Replace("\r", "\\r").Replace("\t", "\\t");
        }

        // WriteCode method.
        private static void WriteCSharpCode(IndentedTextWriter writer, GrammarTextValue code)
        {
            if (!code.SourceInfo.IsUnknown)
            {
                writer.WriteLineNoTabs(String.Format("#line {0} \"{1}\"", code.SourceInfo.Range.Start.LineNumber, code.SourceInfo.SourceIdentity));
                writer.WriteLineNoTabs(new String(' ', code.SourceInfo.Range.Start.ColumnNumber - 1) + code.Text);
                writer.WriteLineNoTabs("#line default");
            }
            else
            {
                writer.WriteLine(code.Text);
            }
        }
    }
}
